<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>src\database.js - GIAnT API</title>
    <link rel="stylesheet" href="http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css">
    <link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css">
    <link rel="stylesheet" href="../assets/css/main.css" id="site_styles">
    <link rel="icon" href="../assets/favicon.ico">
    <script src="http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js"></script>
</head>
<body class="yui3-skin-sam">

<div id="doc">
    <div id="hd" class="yui3-g header">
        <div class="yui3-u-3-4">
                <h1><img src="../assets/css/logo.png" title="GIAnT API" width="117" height="52"></h1>
        </div>
        <div class="yui3-u-1-4 version">
            <em>API Docs for: 1.0.0</em>
        </div>
    </div>
    <div id="bd" class="yui3-g">

        <div class="yui3-u-1-4">
            <div id="docs-sidebar" class="sidebar apidocs">
                <div id="api-list">
                    <h2 class="off-left">APIs</h2>
                    <div id="api-tabview" class="tabview">
                        <ul class="tabs">
                            <li><a href="#api-classes">Classes</a></li>
                            <li><a href="#api-modules">Modules</a></li>
                        </ul>
                
                        <div id="api-tabview-filter">
                            <input type="search" id="api-filter" placeholder="Type to filter APIs">
                        </div>
                
                        <div id="api-tabview-panel">
                            <ul id="api-classes" class="apis classes">
                                <li><a href="../classes/Atom.html">Atom</a></li>
                                <li><a href="../classes/Codec.html">Codec</a></li>
                                <li><a href="../classes/Constraints.html">Constraints</a></li>
                                <li><a href="../classes/Database.html">Database</a></li>
                                <li><a href="../classes/Exif.html">Exif</a></li>
                                <li><a href="../classes/Export.html">Export</a></li>
                                <li><a href="../classes/Heatmap.html">Heatmap</a></li>
                                <li><a href="../classes/Server.html">Server</a></li>
                                <li><a href="../classes/Settings.html">Settings</a></li>
                                <li><a href="../classes/Utils.html">Utils</a></li>
                            </ul>
                
                
                            <ul id="api-modules" class="apis modules">
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="yui3-u-3-4">
                <div id="api-options">
                    Show:
                    <label for="api-show-inherited">
                        <input type="checkbox" id="api-show-inherited" checked>
                        Inherited
                    </label>
            
                    <label for="api-show-protected">
                        <input type="checkbox" id="api-show-protected">
                        Protected
                    </label>
            
                    <label for="api-show-private">
                        <input type="checkbox" id="api-show-private">
                        Private
                    </label>
                    <label for="api-show-deprecated">
                        <input type="checkbox" id="api-show-deprecated">
                        Deprecated
                    </label>
            
                </div>
            
            <div class="apidocs">
                <div id="docs-main">
                    <div class="content">
<h1 class="file-heading">File: src\database.js</h1>

<div class="file">
    <pre class="code prettyprint linenums">
var neo4j = require(&#x27;neo4j-driver&#x27;).v1;
var utils = require(&#x27;./utils&#x27;);
var log = require(&#x27;electron-log&#x27;);
var parse = require(&#x27;exif-date&#x27;).parse;

//var config = require(&#x27;../config.json&#x27;);

function log_error_and_close_session(session) {
    return function(err) {
        log.error(err);
        session.close();
        return err;
    }
}


/**
 *
 * Database
 * --------
 * Contains all the database access methods.
*
 * @class Database
*/
var Database = Database || {};

/**
 * The development flag controls the basic auth credentials
 *
 * @property development
 * @type {boolean}
 * @default false
 */
Database.development = false;

/**
 * Stores the neo4j-driver for the method &lt;_get_driver&gt;
 *
 * @property _driver
 * @type {driver}
 * @private
 * @default null
 */
Database._driver = null;

/**
 * Stores whether a successful login took place
 *
 * @property _logged_in
 * @type {boolean}
 * @default false
 */
Database.logged_in = false;

/**
* Try to fetch a neo4j driver with the given credentials
 * if that works, the Database._driver is not null anymore.
 * It also set the Database.logged_in to true
 *
* @method login
* @param url {string} the endpoint of neo4j
* @param user {string}
* @param password {string}
* @return {boolean|error}
*/
Database.login = function (url, user, password){
    var p = new Promise(function(resolve, reject) {
        // the _driver is the actual flag for the state
        if (Database._driver !== null) {
            Database.logged_in = true;
            resolve();
        } else {
            try {
                Database._driver = neo4j.driver(url, neo4j.auth.basic(user, password));
                /*Database._driver.onCompleted = function () {
                    console.log(&quot;ON COMP&quot;);
                };*/

                Database._driver.onError = function (error) {
                    Database._driver = null;
                    Database.logged_in = false;
                    Database._driver = null;
                    return reject(error);
                };

                var session = Database._get_session();
                // Execute a small query to see whether the connection works
                return session.run(&#x27;return 1&#x27;).then(
                    function() {
                        Database.logged_in = true;
                        return Database._hygiene().then(function(res) {
                            return resolve();
                        },
                        function(err){
                            Database._driver = null;
                            Database.logged_in = false;
                            log.error(err);
                            return reject(err);
                        });
                    },
                    function(err) {
                        Database._driver = null;
                        Database.logged_in = false;
                        reject(err);
                    }
                );
            } catch (err) {
                Database._driver = null;
                Database.logged_in = false;
                return reject(err);
            }
        }
    });
    return p;
};



/**
 * Logout from the current database neo4j session.
 * Delete the saved neo4j session and set the Database.logged_in flag to false.
 *
* @method logout
* @return {boolean}
*/
Database.logout = function (){
    var driver = Database._get_driver();
    if (driver) {
        log.info(&quot;... neo4j driver closed&quot;)
        driver.close();
    }
    Database._driver = null;
    Database.logged_in = false;
};

/**
 * Generic catch error function for database promises
 * Logs the error and closes the db session
 *
* @method log_error_and_close_session
* @return {error}
*/
Database.log_error_and_close_session = log_error_and_close_session;

/**
* Get the database driver
 * stores the instance in _driver
 * makes use of the development flag in order to use or not use basic auth
 *
* @method _get_driver
 * @private
* @return {driver} Returns a bolt:// driver instance
*/
Database._get_driver = function (){
    if (this._driver !== null) {
        return this._driver;
    }
    return false;
};

/**
 * Get a session to execute Neo4J Cypher code on
 *
 * @method _get_session
 * @private
 * @return {session}
 */
Database._get_session = function () {
    var driver = Database._get_driver();
    if (!driver) {
        log.error(&quot;Could not fetch driver&quot;);
        return null;
    } else {
        return driver.session();
    }

};

/**
 * Add the necessary constraints to create something like a schema
 * 1) The file_path of an image is unique
 *
 * @method add_constraints
 * @return {session}
 */
Database._add_constraints = function() {
    var session = this._get_session();
    return session.run(&quot;CREATE CONSTRAINT ON (i:Image) ASSERT i.file_path IS UNIQUE; &quot;);
};

/**
 * Clean up the database on login
 *
 * 1) remove nodes without edges (-&gt; MetaGroups that are not referenced)
 *
* @method _hygiene
* @return {Promise}
*/
Database._hygiene = function (){
    var session = Database._get_session();
    // this query removes all free swimming nodes (without any edges)
    return session.run(&quot;match (n) where not (n)--() delete (n);&quot;);
};

/**
 * Remove the constraints
 *
 * @method _remove_constraints
 * @private
 * @return {Promise}
 */
Database._remove_constraints = function() {
    var session = Database._get_session();
    return session.run(&quot;DROP CONSTRAINT ON (i:Image) ASSERT i.file_path IS UNIQUE; &quot;);
};

/**
 * Do the initialization on start
 * 1) drop and then create constraint
 *
 * @method init
 * @return {session}
 */
Database.init = function(callback) {
    return Database._remove_constraints().then(function(){
        return Database._add_constraints()
    }, function(){
        return Database._add_constraints()
    });
};

/**
 * Toggle the completed status of a fragment
 * The status is used for the batch-add functionality
 *
 * @method toggle_fragment_completed
 * @return {Promise}
 */
Database.toggle_fragment_completed = function(image_id, fragment_id) {
    var session = Database._get_session();
    var prom = session.run(&quot;MATCH (a:Fragment)-[r:image]-&gt;(i:Image) &quot; +
        &quot;WHERE ID(a) = toInteger({fragment_id}) AND ID(i) = toInteger({image_id}) &quot; +
        &quot;SET a.completed = NOT a.completed &quot; +
        &quot;RETURN a;&quot;,
        {image_id: Number(image_id), fragment_id: Number(fragment_id)})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records[0];
        }, log_error_and_close_session(session));
    return prom;
};


/**
 * Adds an image to the database
 * Sets attributes according to the exif_data
 * Fallback for upload_date is now in seconds (Math.round(d.getTime()))
 *
 * The exif_data shall be an objects with the attributes
 *    &#x27;exif: {
 *         &#x27;ExifImageWidth&#x27;: ...,
 *         &#x27;ExifImageHeight&#x27;: ...,
 *         &#x27;CreateDate&#x27;: ...
 *     }, &#x27;gps: {...}
 *
 * CreateDate is handled by the parse(...) function
 *
 * @method add_image
 * @param file_path {string} The unique identifier for an image
 * @param exif_data
 * @return {Promise}
 */
Database.add_image = function(file_path, exif_data) {
    var session = this._get_session();
    var d = new Date();
    // This is the fallback for the upload_date
    var upload_date = Math.round(d.getTime());
    var cql = &quot;CREATE (a:Image {file_path: {file_path}, upload_date: {upload_date}}) RETURN ID(a) as ident;&quot;;
    var meta_data = null;
    if (exif_data) {
        cql = &quot;CREATE (a:Image {file_path: {file_path}, upload_date: {upload_date}}) &quot; +
            &quot;SET a += {meta_data} &quot; +
            &quot;RETURN ID(a) as ident;&quot;;
        meta_data =  exif_data[&#x27;gps&#x27;] || {};
        if (exif_data.hasOwnProperty(&#x27;exif&#x27;) &amp;&amp; exif_data[&#x27;exif&#x27;].hasOwnProperty(&#x27;ExifImageWidth&#x27;)
            &amp;&amp; exif_data[&#x27;exif&#x27;].hasOwnProperty(&#x27;ExifImageHeight&#x27;)) {
            Object.assign(meta_data, {
                width:  exif_data.exif.ExifImageWidth,
                height: exif_data.exif.ExifImageHeight
            });
        }
        // Get the upload_date from the creation date exif tag
        if (exif_data &amp;&amp; exif_data.hasOwnProperty(&#x27;exif&#x27;) &amp;&amp; exif_data[&#x27;exif&#x27;].hasOwnProperty(&#x27;CreateDate&#x27;)) {
            // might look like this 2017:05:28 19:46:49
            try {
                var raw_format = exif_data[&#x27;exif&#x27;][&#x27;CreateDate&#x27;];
                if (raw_format.indexOf(&quot; &quot;) &gt;= 0) {
                    var parsed_date = parse(raw_format);
                    if (parsed_date) {
                        upload_date = Math.round(parsed_date.getTime());
                    }
                }
            } catch (e) {
                log.error(e);
            }
        }
        Object.keys(meta_data).forEach(function(key) {
            meta_data[key] = meta_data[key].valueOf();
            if  (meta_data[key] instanceof Buffer) {
                meta_data[key] = meta_data[key].toString();
            }
        });
    }

    var p = session.run(cql,
        {file_path: file_path, upload_date: upload_date, meta_data: meta_data})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            if (records.length === 0) {
                throw Error(&#x27;Zero elements created&#x27;);
            }
            return records[0];
        }, log_error_and_close_session(session));
    return p;
};

/**
 * Get image from database by file_path
 *
 * Promise: success: Contains record for the image
 *
 * @method get_image
 * @param file_path {string} The unique identifier for an image
 * @return {Promise}
 */
Database.get_image = function(file_path){
    // Duplications are silent for some reason
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (a:Image {file_path: {file_path}}) return ID(a) as ident, a;&quot;, {file_path: file_path})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records[0];
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Get the image of a token
 *
 * @method get_image_of_token
 * @param token_id {int} The unique identifier for a token
 * @return {Promise}
 */
Database.get_image_of_token = function(token_id){
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (t:Token)-[]-(f:Fragment)-[]-(i:Image) WHERE ID(t) = toInteger({token_id}) RETURN i, ID(f) as fragment_id, ID(i) AS image_id;&quot;,
            {token_id: Number(token_id)})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records[0];
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Get the bounding box of a fragment
 *
 * This method looks for the lowest x and y coordinates of all sibling tokens
 * and for the highest x and y coordinates of all siblings
 *
 * Used for heat map generation
 *
 * @method get_fragment_bounding_box
 * @param token_id {int} The unique identifier for a token
 * @return {Promise}
 */
Database.get_fragment_bounding_box = function(token_id){
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (t:Token)-[]-(f:Fragment) WHERE ID(t) = toInteger({token_id}) &quot;+
            &quot;WITH f &quot; +
            &quot;MATCH (x:Token)-[]-(f) &quot; +
            &quot;WITH x, f &quot;+
            &quot;WHERE EXISTS(x.x) AND EXISTS(x.y) AND EXISTS(x.width) AND EXISTS(x.height) &quot;+
            &quot;RETURN MIN(toInteger(x.x)) as x, MIN(toInteger(x.y)) as y, &quot; +
            &quot;MAX(toInteger(x.x)+toInteger(x.width)) as width, MAX(toInteger(x.y)+toInteger(x.height)) as height, ID(f) AS fragment_id;&quot;,
            {token_id: Number(token_id)})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            if (records.length === 0) {
                return null;
            }
            return records[0];
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Removes an image by ID
 *
 * @method remove_image_by_id
 * @param id_ {String|Number}
 * @return {Promise}
 */
Database.remove_image_by_id = function (id_) {
    var session = this._get_session();
    return session.run(
        &quot;MATCH (n:Image) &quot; +
        &quot;WHERE ID(n) = toInteger({ident}) &quot; +
        &quot;RETURN n.file_path as file_path&quot;, {ident: Number(id_)})
        .then(function (result) {
            var file_path = result.records[0].get(&quot;file_path&quot;);
            try {
                utils.remove_image(file_path);
            } catch (e) {
                log.warn(&quot;Image node is being deleted but there was an error finding the local image file: &quot;+e);
            }
            return session
                .run(&quot;MATCH (n:Image)&lt;-[:image]-(f:Fragment)&lt;-[:fragment]-(t) &quot; +
                    &quot;WHERE ID(n) = toInteger({ident}) &quot; +
                    &quot;DETACH DELETE t;&quot;, {ident: Number(id_)})
                .then(function (result) {
                    return session.run(
                        &quot;MATCH (n:Image)-[:image]-(f:Fragment) &quot; +
                        &quot;WHERE ID(n) = toInteger({ident}) &quot; +
                        &quot;DETACH DELETE n,f&quot;, {ident: Number(id_)})
                        .then(function (result) {
                            return session.run(
                                &quot;MATCH (n:Image) &quot; +
                                &quot;WHERE ID(n) = toInteger({ident}) &quot; +
                                &quot;DETACH DELETE n;&quot;, {ident: Number(id_)})
                                .then(function (result) {
                                    return session.run(
                                        &quot;MATCH (a:MetaGroup) WHERE not ((a)--()) DELETE a;&quot;)
                                        .then(function (result) {
                                            session.close();
                                            return result;
                                        }, log);
                                }, log_error_and_close_session(session));

                        }, log_error_and_close_session(session));
                }, log_error_and_close_session(session));
        }, log_error_and_close_session(session));
};

/**
 * Adds a fragment to an image
 * Every image can have multiple fragments
 * the name of the fragment is unique
 *
 * @method add_fragment
 * @param image_id
 * @param fragment_name The identifier for the fragment
 * @return {Promise}
 */
Database.add_fragment = function(image_id, fragment_name) {
    var session = this._get_session();
    var d = new Date();
    var upload_date = Math.round(d.getTime());
    return session.run(&quot;MATCH (i:Image) &quot; +
        &quot;WHERE ID(i) = toInteger({image_id}) &quot; +
        &quot;WITH i &quot; +
        &quot;CREATE (f:Fragment {fragment_name: {fragment_name}, upload_date: {upload_date}, completed:false, comment:{comment}})-[:image]-&gt;(i) &quot; +
        &quot;RETURN ID(f) as ident, ID(i) AS image_ident;&quot;,
        {fragment_name: fragment_name, upload_date: upload_date, image_id: Number(image_id), comment: &#x27;&#x27;})
        .then(function(result){
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records[0];
        }, log_error_and_close_session(session))
};

/**
 * Sets the comment attribute of a fragment
 *
 * @method add_comment_to_fragment
 * @param fragment_id
 * @param comment string
 * @return {Promise}
 */
Database.add_comment_to_fragment = function(fragment_id, comment) {
    var session = this._get_session();
    return session.run(
        &quot;MATCH (f:Fragment) &quot; +
        &quot;WHERE ID(f) = toInteger({fragment_id}) &quot; +
        &quot;SET f += {comment: {comment}} &quot; +
        &quot;RETURN ID(f) as ident;&quot;,
        {
            fragment_id: fragment_id,
            comment: comment
        }
    ).then(function(result){
        session.close();
        var records = [];
        for (var i = 0; i &lt; result.records.length; i++) {
            records.push(result.records[i]);
        }
        return records[0];
    }, log_error_and_close_session(session));

};

/**
 * Removes a fragment of an image or makes it empty
 *
 * This method not only deletes the fragment but also its children
 *
 * @method remove_fragment
 * @param image_id
 * @param fragment_id
 * @param dont_delete_fragment Activate this option to delete only the children
 * @return {Promise}
 */
Database.remove_fragment = function(image_id, fragment_id, dont_delete_fragment) {
    if (dont_delete_fragment === undefined) {dont_delete_fragment=true;}
    var session = this._get_session();
    return session.run(&quot;MATCH (i:Image)&lt;-[:image]-(f:Fragment)-[:fragment]-(t) &quot; +
        &quot;WHERE ID(i) = toInteger({image_id}) AND ID(f) = toInteger({fragment_id}) &quot; +
        &quot;DETACH DELETE t;&quot;, {image_id: Number(image_id), fragment_id:Number(fragment_id)})
        .then(function(success) {
                if (!dont_delete_fragment) {
                    return session.run(&quot;MATCH (i:Image)&lt;-[:image]-(f:Fragment) &quot; +
                        &quot;WHERE ID(i) = toInteger({image_id}) AND ID(f) = toInteger({fragment_id}) &quot; +
                        &quot;DETACH DELETE f;&quot;, {image_id: Number(image_id), fragment_id: Number(fragment_id)})
                        .then(
                            function (result) {
                                return session.run(
                                    &quot;MATCH (a:MetaGroup) WHERE not ((a)--()) DELETE a;&quot;)
                                    .then(function (result) {
                                        session.close();
                                        return result;
                                    }, log_error_and_close_session(session));
                            }, log_error_and_close_session(session)
                        );
                } else {
                    var hash = utils.hash_xml_fragment(fragment_id);
                    return session.run(&quot;MATCH (i:Image)&lt;-[:image]-(f:Fragment) &quot; +
                        &quot;WHERE ID(i) = toInteger({image_id}) AND ID(f) = toInteger({fragment_id}) &quot; +
                        &quot;SET f.hash = {hash};&quot;, {image_id: Number(image_id), fragment_id: Number(fragment_id),
                                                hash: hash})
                        .then(
                            function (result) {
                                session.close();
                                return result;
                            }, log_error_and_close_session(session)
                        );
                }
            }, log_error_and_close_session(session)

        );
};

/**
 * Get the fragment of an image by name
 *
 * @method get_fragment
 * @param image_file_path {String}  The unique id for the image
 * @param fragment_name The identifier for the fragments
 * @return {Promise}
 */
Database.get_fragment = function(image_file_path, fragment_name) {
    var session = this._get_session();
    var prom = session.run(&quot;MATCH (a:Fragment {fragment_name: {fragment_name}})-[r:image]-&gt;(i:Image {file_path:{file_path}}) &quot; +
        &quot;RETURN ID(a) as ident, ID(i) as image_id, a.fragment_name AS fragment_name, a.upload_date AS upload_date, a.completed AS completed, a.comment as comment;&quot;,
        {fragment_name: fragment_name, file_path: image_file_path})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records[0];
        });
    return prom;
};




/**
 * Get the fragment of an image by name
 *
 * @method get_fragment_by_id
 * @param image_id
 * @param fragment_id
 * @return {Promise}
 */
Database.get_fragment_by_id = function(image_id, fragment_id) {
    var session = this._get_session();
    var prom = session.run(&quot;MATCH (a:Fragment)-[r:image]-&gt;(i:Image) &quot; +
        &quot;WHERE ID(a) = toInteger({fragment_id}) AND ID(i) = toInteger({image_id}) &quot; +
        &quot;RETURN ID(a) as ident, ID(i) as image_id, a.fragment_name AS fragment_name, a.upload_date AS upload_date, a.completed AS completed, a.comment AS comment;&quot;,
        {image_id: Number(image_id), fragment_id: Number(fragment_id)})
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records[0];
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Get the fragment of an image by name
 *
 * @method get_all_fragments
 * @return {Promise}
 */
Database.get_all_fragments = function() {
    var session = this._get_session();
    var prom = session.run(&quot;MATCH (f:Fragment) RETURN ID(f) as identifier;&quot;)
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(Number(result.records[i].get(&#x27;identifier&#x27;)));
            }
            return records;
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Adds a node to a fragment of an image
 *
 * Promise:success  number_of_created_nodes
 *
 * @method add_node
 * @param image_id
 * @param fragment_id
 * @param node_label
 * @param node_attributes
 * @return {Promise}
 */
Database.add_node = function(image_id, fragment_id, node_label, node_attributes) {
    // check if all the necessary attributes are there
    if (![&#x27;Token&#x27;, &#x27;Group&#x27;].includes(node_label)) {
        throw Error(&#x27;Invalid node label (add_node)&#x27;);
    }
    var enumerator = node_attributes.id;
    var session = this._get_session();
    var query = &quot;MATCH (i:Image)&lt;-[:image]-(f:Fragment) &quot; +
        &quot;WHERE ID(i) = toInteger({image_id}) AND ID(f) = toInteger({fragment_id}) &quot; +
        &quot;WITH f &quot; +
        &quot;CREATE (n:&quot; + node_label + &quot; {enumerator:{enumerator}})-[:fragment]-&gt;(f) &quot; +
        &quot;SET n += {props} &quot; +
        &quot;RETURN ID(n) as ident;&quot;;
    var prom = session.run(query,
        {fragment_id: Number(fragment_id), image_id: Number(image_id), enumerator:Number(enumerator),
        props:node_attributes, node_label: node_label})
        .then(function (result) {
            session.close();
            if (node_attributes.hasOwnProperty(&#x27;groupType&#x27;)) {
                if (node_attributes.groupType === &#x27;frame&#x27;) {
                    var group_id = Number(result.records[0].get(&#x27;ident&#x27;));
                    return Database.add_frame_edge(group_id, node_attributes.value, &#x27;MetaFrame&#x27;);
                }
            }
            return result;
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Adds an edge between a frame from a group and a global frame
 * so called &#x27;meta frame&#x27;
 *
 * Also creates the &#x27;meta frame&#x27; if it does not exist *
 *
 * @method add_edge
 * @return {Promise}
 * @param node_id
 * @param frame_name
 * @param groupType
 */
Database.add_frame_edge = function(node_id, frame_name, groupType) {
    var session = this._get_session();
    var prom = session.run(
        &quot;MATCH (b:Group) &quot; +
        &quot;WHERE ID(b) = toInteger({node_id}) &quot; +
        &quot;WITH b &quot; +
        &quot;MERGE (m:MetaGroup {value:{frame_name}, groupType:{groupType}}) &quot; +
        &quot;CREATE (m)&lt;-[n:PartOf]-(b) &quot; +
        &quot;&quot;,
        {node_id: node_id, frame_name:frame_name, groupType:groupType})
        .then(function (result) {
            session.close();
            return result;
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Adds an edge between two nodes
 * in a fragment of an image
 *
 * Promise:success  number_of_created_edges
 *
 * @method add_edge
 * @param image_id
 * @param fragment_id
 * @param source_enum
 * @param target_enum
 * @param edge_attributes
 * @return {Promise}
 */
Database.add_edge = function(image_id, fragment_id, source_enum, target_enum, edge_attributes) {
    var enumerator = edge_attributes.id;
    var session = this._get_session();
    var prom = session.run(&quot;MATCH (i:Image)&lt;-[:image]-(f:Fragment) &quot; +
        &quot;WHERE ID(i) = toInteger({image_id}) AND ID(f) = toInteger({fragment_id}) &quot; +
        &quot;WITH f &quot; +
        &quot;MATCH (f)&lt;-[:fragment]-(a {enumerator: {source_enum} }), (f)&lt;-[:fragment]-(b {enumerator: {target_enum} }) &quot; +
        &quot;CREATE (a)-[n:edge]-&gt;(b) &quot; +
        &quot;SET n += {props};&quot;,
        {fragment_id: Number(fragment_id), image_id: Number(image_id),
            source_enum:Number(source_enum), target_enum:Number(target_enum),
            props:edge_attributes
        })
        .then(function (result) {
            session.close();
            return result;
        }, log_error_and_close_session(session));
    return prom;
};


/**
 * Returns a promise with an array of records of images
 * ordered by last_edit_date, upload_date
 *
 * @example
       database.get_all_images().then(function (results) {
            var row_data = [];
            results.forEach(function (r) {
                row_data.push([r.get(&#x27;ident&#x27;), r.get(&#x27;file_path&#x27;), r.get(&#x27;upload_date&#x27;), r.get(&#x27;completed&#x27;)]);
            });
            res.render(&#x27;table&#x27;,
                {
                    title: &#x27;Hey&#x27;,
                    message: &#x27;Hello there!&#x27;,
                    rows: row_data
                });
            }
        );
 *
 * @method get_all_images
 * @return {Promise}
 */
Database.get_all_images = function() {
    // pagination is not necessary so far (the page behaved well with 300 images in a test)
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (a:Image) &quot; +
            &quot;OPTIONAL MATCH (a)-[]-(f1:Fragment {completed:false}) &quot; +
            &quot;OPTIONAL MATCH (a)-[]-(f2:Fragment {completed:true}) &quot; +
            &quot;WITH a, f1, f2 &quot; +
            &quot;ORDER BY a.last_edit_date, a.upload_date &quot; +
            &quot;RETURN &quot; +
            &quot;ID(a) as ident,&quot; +
            &quot;a.file_path as file_path, &quot; +
            &quot;a.upload_date as upload_date, &quot; +
            &quot;COUNT(f1) AS num_uncompleted_fragments,&quot; +
            &quot;COUNT(f2) AS num_completed_fragments;&quot;)
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records;
        });
    return prom;
};

/**
 * Get all fragments of one image
 *
 * @method get_fragments_by_image_id
 * @param image_id
 * @return {*|Promise}
 */
Database.get_fragments_by_image_id = function(image_id) {
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (a:Image)-[r]-(f:Fragment) &quot; +
            &quot;WHERE ID(a) = toInteger({image_id})&quot; +
            &quot;WITH a,f &quot; +
            &quot;ORDER BY f.upload_date &quot; +
            &quot;RETURN &quot; +
            &quot;ID(a) as image_id,&quot; +
            &quot;a.file_path as file_path, &quot; +
            &quot;ID(f) as fragment_id, &quot; +
            &quot;f.fragment_name as fragment_name, &quot; +
            &quot;f.upload_date as upload_date, &quot; +
            &quot;f.completed as completed, &quot; +
            &quot;f.comment as comment;&quot;,
            {image_id: Number(image_id)}
        )
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records;
        });
    return prom;
};

/**
 * Get all completed fragments
 *
 * @method get_all_completed_fragments
 * @return {*|Promise}
 */
Database.get_all_completed_fragments = function() {
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (a:Image)-[r]-(f:Fragment {completed:true}) &quot; +
            &quot;WITH a,f &quot; +
            &quot;RETURN &quot; +
            &quot;ID(a) as image_id, &quot; +
            &quot;f.hash as hash, &quot; +
            &quot;ID(f) as fragment_id; &quot;)
        .then(function (result) {
            session.close();
            var records = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                records.push(result.records[i]);
            }
            return records;
        }, log_error_and_close_session(session));
    return prom;
};

/**
 * Returns all possible property keys for tokens filtered by search_string
 * Is used for the autocomplete function of the add-property-input
 *
 * Promise:success: Array of strings
 *
 * @method get_all_property_keys_for_token
 * @param search_string {string} Empty string gives all possible values
 * @param label
 * @param token_type
 * @return {Promise}
 */
Database.get_all_property_keys_for_token = function(search_string, label, token_type) {
    if (label === undefined) {
        label = &#x27;Token&#x27;
    }
    var extra_check = &quot;&quot;;
    if (token_type !== undefined) {
        if (label === &#x27;Token&#x27;) {
            extra_check = &#x27; {tokenType:&quot;&#x27;+token_type+&#x27;&quot;}&#x27;;
        } else if (label === &#x27;Group&#x27;) {
            extra_check = &#x27; {groupType:&quot;&#x27;+token_type+&#x27;&quot;}&#x27;;
        }
    }

    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (p:&quot;+ label +&quot;&quot;+ extra_check +&quot;)  WITH DISTINCT keys(p) AS keys &quot; +
            &quot;UNWIND keys AS keyslisting WITH DISTINCT keyslisting AS allfields &quot; +
            &quot;WHERE allfields CONTAINS {search_string}&quot; +
            &quot;RETURN allfields;&quot;, {search_string: search_string || &#x27;&#x27;,
                                  token_type: token_type
        })
        .then(function (result) {
            session.close();
            var keys = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                keys.push(result.records[i][&#x27;_fields&#x27;][0]);
            }
            return keys;
        });
    return prom;
};

/**
 * Returns all distinct values for the given property in a string list in a promise
 * Is used for the autocomplete function of the property values
 *
 * Promise:success: Array of strings *
 *
 * @method get_all_property_values_for_token
 * @param property {string} the name of a property of tokens
 * @param search_string {string} that filters the result (empty string is not filter)
 * @return {Promise}
 */
Database.get_all_property_values_for_token = function(property, search_string, label) {
    if (label === undefined) {
        label = &#x27;Token&#x27;
    }
    var session = this._get_session();
    var prom = session
        .run(&quot;MATCH (n:&quot;+label+&quot;) &quot; +
            &quot;WHERE n[{property}] IS NOT NULL &quot; +
            &quot;AND n[{property}] CONTAINS {search_string} &quot; +
            &quot;RETURN distinct n[{property}];&quot;,
            {property: property,
            search_string: search_string || &#x27;&#x27;}
        )
        .then(function (result) {
            session.close();
            var values = [];
            for (var i = 0; i &lt; result.records.length; i++) {
                values.push(result.records[i][&#x27;_fields&#x27;][0]);
            }
            return values;
        },
        log_error_and_close_session(session));
    return prom;
};

module.exports = Database;
    </pre>
</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="../assets/vendor/prettify/prettify-min.js"></script>
<script>prettyPrint();</script>
<script src="../assets/js/yui-prettify.js"></script>
<script src="../assets/../api.js"></script>
<script src="../assets/js/api-filter.js"></script>
<script src="../assets/js/api-list.js"></script>
<script src="../assets/js/api-search.js"></script>
<script src="../assets/js/apidocs.js"></script>
</body>
</html>
